using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

namespace shaco.Instance.Editor
{
    static public class EditorCoroutineHelper
    {
        static float s_currentDeltaTime;
        static DateTime s_nowTime = DateTime.Now;


        public class EditorWaitForSeconds : IEnumerator
        {
            public float timer;
            public object Current => null;

            public EditorWaitForSeconds(float time)
            {
                timer = time;
            }

            public bool MoveNext()
            {
                timer -= s_currentDeltaTime;
                return timer > 0;
            }

            public void Reset()
            {
                timer = 0;
            }
        }

        private class EditorCoroutine : IEnumerator
        {
            public IEnumerator key;
            private Stack<IEnumerator> executionStack;

            public EditorCoroutine(IEnumerator iterator)
            {
                this.key = iterator;
                this.executionStack = new Stack<IEnumerator>();
                this.executionStack.Push(iterator);
            }

            public bool MoveNext()
            {
                IEnumerator i = this.executionStack.Peek();

                if (i.MoveNext())
                {
                    object result = i.Current;
                    if (result != null)
                    {
                        if (result is IEnumerator)
                            this.executionStack.Push((IEnumerator)result);
                        else if (result is WaitForSeconds)
                        {
                            var filedTmp = result.GetType().GetField("m_Seconds", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
                            var waitSeconds = (float)filedTmp.GetValue(result);
                            this.executionStack.Push(new EditorWaitForSeconds(waitSeconds));
                        }
                        else if (result is AsyncOperation)
                        {
                            var operationTmp = result as AsyncOperation;
                            this.executionStack.Push(new WaitUntil(() =>
                            {
                                return operationTmp.isDone;
                            }));
                        }
                    }

                    return true;
                }
                else
                {
                    if (this.executionStack.Count > 1)
                    {
                        this.executionStack.Pop();
                        return true;
                    }
                }

                return false;
            }

            public void Reset()
            {
                throw new System.NotSupportedException("This Operation Is Not Supported.");
            }

            public object Current
            {
                get { return this.executionStack.Peek().Current; }
            }

            public bool Find(IEnumerator iterator)
            {
                return this.executionStack.Contains(iterator);
            }
        }

        static private List<EditorCoroutine> editorCoroutineList;
        static private List<IEnumerator> buffer;

        static public IEnumerator StartCoroutine(IEnumerator iterator)
        {
            if (editorCoroutineList == null)
            {
                editorCoroutineList = new List<EditorCoroutine>();
                EditorApplication.update -= EditorUpdate;
                EditorApplication.update += EditorUpdate;
            }
            if (buffer == null)
            {
                buffer = new List<IEnumerator>();
            }

            buffer.Add(iterator);
            return iterator;
        }

        static public void StopCoroutine(IEnumerator iterator)
        {
            if (null != buffer)
                buffer.Remove(iterator);
            if (null != editorCoroutineList)
            {
                var index = editorCoroutineList.FindIndex(v => v.key == iterator);
                if (index >= 0)
                    editorCoroutineList.RemoveAt(index);
            }
        }

        static private bool Find(IEnumerator iterator)
        {
            // If this iterator is already added
            // Then ignore it this time
            foreach (EditorCoroutine editorCoroutine in editorCoroutineList)
            {
                if (editorCoroutine.Find(iterator))
                {
                    return true;
                }
            }

            return false;
        }

        static void EditorUpdate()
        {
            var curTotalSeconds = (float)(DateTime.Now - s_nowTime).TotalSeconds;
            s_nowTime = DateTime.Now;
            MainUpdate(curTotalSeconds);
        }

        static void MainUpdate(float dt)
        {
            if (null == editorCoroutineList)
                return;

            s_currentDeltaTime = dt;

            // If we have iterators in buffer
            if (buffer.Count > 0)
            {
                foreach (IEnumerator iterator in buffer)
                {
                    // If this iterators not exists
                    if (!Find(iterator))
                    {
                        // Added this as new EditorCoroutine
                        editorCoroutineList.Add(new EditorCoroutine(iterator));
                    }
                }

                // Clear buffer
                buffer.Clear();
            }

            // EditorCoroutine execution may append new iterators to buffer
            // Therefore we should run EditorCoroutine first
            editorCoroutineList.RemoveAll
            (
                coroutine => { return coroutine.MoveNext() == false; }
            );

            if (0 == editorCoroutineList.Count)
            {
                editorCoroutineList = null;
                buffer = null;
                EditorApplication.update -= EditorUpdate;
            }
        }
    }
}